package com.mswyt.utils.msHandlebean;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.mswyt.utils.base.MsAssertUtils;
import com.mswyt.utils.base.MsEmptyUtils;
import com.mswyt.utils.base.MsIocUtils;
import com.mswyt.utils.base.MsReflexUtils;
import com.mswyt.utils.config.cache.MsCache;
import com.mswyt.utils.config.constants.ApplicationConstant;
import com.mswyt.utils.msCollection.MsMap;
import com.mswyt.utils.msConverts.MsConvertDataUtils;
import com.mswyt.utils.msHandlebean.annotation.*;
import com.mswyt.utils.msHandlebean.dto.MsDelCheckDto;
import com.mswyt.utils.msHandlebean.dto.MsDictDto;
import com.mswyt.utils.msHandlebean.dto.MsJoinCacheDto;
import com.mswyt.utils.msHandlebean.dto.MsSaveCheckDto;
import org.apache.commons.collections.MapUtils;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

/**
 * @author: mashuai
 * @ClassName: MsHandleDataUtils
 * @Description: 处理工具类
 */
@Component
public class MsHandleDataUtils implements Serializable {

    //handle下面注解一些默认值
    private final static String DICT_NEED_FIELD = ApplicationConstant.msHbDictNeedFiled;
    private final static String DICT_SPLIT_VAL = ApplicationConstant.msHbDictSplit;
    private final static String DEL_DEL_FIELD = ApplicationConstant.msHbDelSing;
    private final static String TB_JOIN_FIELD = ApplicationConstant.msHbTbJoinField;
    private final static String SAVE_BIZ_FILED = ApplicationConstant.msHbSaveBizFiled;
    //异常提示信息
    private final static String NO_DICT_IMPL = "MsJoinHandleUtils中有使用字典处理方法，但没找到实现IMsGetDict接口的类，无法获取字典值！请重写其getDictData方法，对字典值进行处理！";
    private final static String NO_SAVE_CHECK_TABLE = "未指定校验表名，请在实体类上配置@TableName指定表名/字段上配置@MsSaveCheck(checkTable='检测表名')";
    private final static String NO_DEL_CHECK_NO_CT = "使用删除检验方法，请在需要检验字段上加上@MsDelCheck注解，checkTable={'检查表明@检查表的字段@如果被引用进行提示的字段'}";
    private final static String DEL_CHECK_NO_ARRAYS = "请使用List<T> list = new ArrayList()创建集合，不要用Arrays.asList(T...)创建长度不可变List集合！";
    private static final String LE_MS_JOIN_TABLE = "使用@MsJoinMoreTables注解表示要出现多个left join，里面的@@MsJoinTable注解必须配置两个以上，否则请使用@MsJoinTable注解即可！";

    //缓存class类@MsJoinTable，@MsJoinSubTable相关注解的字段：key=class.getName(),value=字段集合
    private static Map<String, MsJoinCacheDto> joinCacheDtoMap = MsCache.joinCacheDtoMap;
    //字典值缓存,
    private static Map<String, List<MsDictDto>> dictCacheMap = MsCache.dictCacheMap;
    //sql缓存，
    private static Map<String, String> sqlCacheMap = MsCache.handBeanSqlCacheMap;
    //字段上解析到的注解值缓存
    private static MsMap<String, String, Annotation> annotationsCacheMap = MsCache.handBeanAnnotationCacheMap;


    /**
     * 获取字典值：实现方式由用户自己实现。
     **/
    public static Map<String, List<MsDictDto>> getDictData(List<String> dictTypeList) {
        //是否开启字典缓存
        if (dictCacheMap.size() != 0) {
            Map<String, List<MsDictDto>> cacheDictData = new ConcurrentHashMap<>();
            for (String dictType : dictTypeList) {
                List<MsDictDto> dictData = dictCacheMap.get(dictType);
                if (dictData != null && dictData.size() != 0) {
                    cacheDictData.put(dictType, dictData);
                }
            }
            //判读所需的数据是否全部获取成功
            if (dictTypeList.size() == cacheDictData.size()) return cacheDictData;
        }
        if (dictTypeList.size() == 0) return new HashMap<>();
        String[] beanNames = MsIocUtils.getBeanNames(IMsGetDict.class);
        if (beanNames == null || beanNames.length != 2) MsAssertUtils.throwException(NO_DICT_IMPL);
        IMsGetDict b = MsIocUtils.getBean(beanNames[0]);
        Map<String, List<MsDictDto>> dictData = b.getDictData(dictTypeList);
        if ("true".equals(ApplicationConstant.msCacheDict)) {
            dictCacheMap.putAll(dictData);
        }
        return dictData;
    }

    /**
     * 处理表关系、字典映射
     *
     * @param m     实体类
     * @param clazz 实体类 类对象
     * @return
     * @author mashuai
     */
    public static <M> void handleTbOrDictValBeans(M m, Class<M> clazz) {
        if (null != m) handleTbOrDictValBeans(Arrays.asList(m), clazz);
    }

    /**
     * 处理集合
     **/
    public static <M> void handleTbOrDictValBeans(List<M> list, Class<M> clazz) {
        if (list == null || list.size() == 0) return;
        handleTbOrDictValBeansM(list, clazz, excludeAnnotation(null));
    }


    /***
     *
     * 增加一些需要排除数据处理的注解，
     * **/
    public static <M> void handleTbOrDictValBeans(M m, Class<M> clazz, Class<? extends Annotation>... excludedAnnotations) {
        if (null != m) handleTbOrDictValBeansM(Arrays.asList(m), clazz, excludeAnnotation(excludedAnnotations));
    }

    public static <M> void handleTbOrDictValBeans(List<M> list, Class<M> clazz, Class<? extends Annotation>... excludedAnnotations) {
        if (list == null || list.size() == 0) return;
        handleTbOrDictValBeansM(list, clazz, excludeAnnotation(excludedAnnotations));
    }


    /***
     * 注解优先级：@MsJoinDict > MsJoinMoreTables > @MsJoinSubTable > MsJoinTable
     *
     * **/
    private static <M> void handleTbOrDictValBeansM(List<M> list, Class<M> clazz, Set<String> excludeAnnotations) {
        try {
            //存在放 M 字段中，含有@MsJoinDict的类型，避免重复db
            List<String> dictTypeList = new CopyOnWriteArrayList<>();
            //存放M字段上含有@MsJoinTable，@MsJoinSubTable、@MsJoinMoreTables注解的字段对象
            List<Field> joinTbAList = new CopyOnWriteArrayList<>();
            //解析class字段，给dictTypeList、joinTbAList进行赋值----初次外都走缓存
            getTbDictClassFieldHandleVal(dictTypeList, joinTbAList, clazz);
            //获取M字段上含有@MsJoinSubTable注解数据，key值为@MsJoinSubTable中的joinField名称，value为joinTable表对应的list集合
            Map<String, List<?>> subMap = new ConcurrentHashMap<>();
            //字段上配置的@MsJoinTable数据，key=@MsJoinTable中的joinTable值，value为joinTable所需的数据
            Map<String, List<Map<String, Object>>> joinTableMap = new ConcurrentHashMap<>();
            //字段上配置@MsJoinMoreTables注解，key=字段名称，value为所需的数据
            Map<String, List<Map<String, Object>>> moreTableMap = new ConcurrentHashMap<>();
            //获取字典数据值--缓存可配置
            Map<String, List<MsDictDto>> dictDataMap = getDictData(dictTypeList);
            //获取数据---所有关联表的注解的数据全部在这里进行获取，
            getJoinTableData(list, joinTbAList, joinTableMap, subMap, moreTableMap, excludeAnnotations);
            //处理数据---对应的数据全部获取完成后，开始处理数据
            handleDictOrTbData(joinTableMap, list, dictDataMap, subMap, moreTableMap, excludeAnnotations);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 保存数据时候，检验字段值是否存在
     * 因涉及到数据回滚操作，这边把异常抛出在方法上throws Exception
     *
     * @param m
     * @param throwException true，直接抛出异常
     * @return
     * @author mashuai
     */
    public static <T> List<MsSaveCheckDto> handleSaveCheck(T m, boolean throwException) throws Exception {
        return handleSaveCheckM(m, throwException);
    }

    public static <M> void handleSaveCheck(M m) throws Exception {
        handleSaveCheckM(m, true);
    }

    /**
     * 检验删除的数据是否被其他表进行引用了
     *
     * @param list           删除的数据集合，一般是List<Id>，如果throwException=false，会把可以删除的id进行返回x
     * @param clazz          类型对象
     * @param throwException 当存在被引用的数据时候，是否抛出异常
     * @return 如果throwException=false，具体不能删除的数据，在 List<MsDelCheckDto>，根据业务自己拼接提示即可
     * @author mashuai
     */

    public static <M, S> List<MsDelCheckDto> handleDelCheck(List<S> list, Class<M> clazz, boolean throwException) {
        return handleDelCheckM(list, clazz, throwException);
    }

    public static <M, S> List<MsDelCheckDto> handleDelCheck(List<S> list, Class<M> clazz) {
        return handleDelCheckM(list, clazz, true);
    }


    /**
     * 获取实体类上需要处理的字典、关联表sql语句等信息。
     * 一个字段上只可生效一做注解。默认@MsJoinDict > @MsJoinSubTable > MsJoinTable
     *
     * @param dictTypeList class字段需要处理的字典类型
     * @param joinTbAList  class字段需要进行关联表映射的数据
     * @param clazz
     * @return
     * @author mashuai
     */

    private static <M> void getTbDictClassFieldHandleVal(List<String> dictTypeList, List<Field> joinTbAList, Class<M> clazz) {
        //尝试从缓存取值，指定class已经解析过的字段.这里只会在第一次解析实体类进入，其余时候，走缓存
        MsJoinCacheDto msJoinCacheDto = joinCacheDtoMap.get(clazz.getName());
        if (msJoinCacheDto != null) {
            msJoinCacheDto.getJoinTbFieldList().forEach(t -> joinTbAList.add(t));
            msJoinCacheDto.getDictTypeList().forEach(t -> dictTypeList.add(t));
            return;
        }
        for (Field f : clazz.getDeclaredFields()) {
            f.setAccessible(true);
            String cacheKey = getCacheKey(f);
            //获取缓存中字典类型
            MsJoinDict dict = f.getAnnotation(MsJoinDict.class);
            if (dict != null) {
                dictTypeList.add(dict.dictType());
                annotationsCacheMap.put(cacheKey, MsJoinDict.class.getName(), dict);
                continue;
            }
            MsJoinMoreTables moreA = f.getAnnotation(MsJoinMoreTables.class);
            if (moreA != null) {
                joinTbAList.add(f);
                annotationsCacheMap.put(cacheKey, MsJoinMoreTables.class.getName(), moreA);
                continue;
            }
            MsJoinSubTable sbA = f.getAnnotation(MsJoinSubTable.class);
            if (sbA != null) {
                joinTbAList.add(f);
                annotationsCacheMap.put(cacheKey, MsJoinSubTable.class.getName(), sbA);
                continue;
            }
            MsJoinTable tb = f.getAnnotation(MsJoinTable.class);
            if (tb != null) {
                joinTbAList.add(f);
                annotationsCacheMap.put(cacheKey, MsJoinTable.class.getName(), tb);
            }
        }
        //缓存此次遍历的class属性值
        msJoinCacheDto = new MsJoinCacheDto();
        msJoinCacheDto.setClassName(clazz.getName());
        msJoinCacheDto.setDictTypeList(dictTypeList);
        msJoinCacheDto.setJoinTbFieldList(joinTbAList);
        joinCacheDtoMap.put(clazz.getName(), msJoinCacheDto);
    }

    /**
     * 处理数据核心方法
     *
     * @param joinDataMap
     * @param list               待处理的数据集合
     * @param dictDataMap        字典map
     * @param excludeAnnotations
     * @return
     * @author mashuai
     */

    private static <M> void handleDictOrTbData(Map<String, List<Map<String, Object>>> joinDataMap, List<M> list, Map<String, List<MsDictDto>> dictDataMap, Map<String, List<?>> subMap, Map<String, List<Map<String, Object>>> moreTableMap, Set<String> excludeAnnotations) throws Exception {
        //遍历需要处理数据的集合每个字段值
        for (M m : list) {
            Field[] declaredFields = m.getClass().getDeclaredFields();
            for (Field f : declaredFields) {
                f.setAccessible(true);
                String cacheKey = getCacheKey(f);
                //如果该字段不是注解类型值，或该注解被跳过了不进行数据处理，直接遍历下一个
                if (!annotationsCacheMap.containsKey(cacheKey) || skipAnnotation(excludeAnnotations, f)) continue;
                Object v = f.get(m);
                //值不为空在进行数据获取
                if (v != null && !"".equals(v)) {
                    MsJoinDict dictA = (MsJoinDict) annotationsCacheMap.get(cacheKey, MsJoinDict.class.getName());
                    //处理数据字典值
                    if (dictA != null && dictDataMap.size() != 0) {
                        handleDictData(m, f, v, dictA, dictDataMap);
                        continue;
                    }
                    //处理多层left join数据
                    MsJoinMoreTables moreA = (MsJoinMoreTables) annotationsCacheMap.get(cacheKey, MsJoinMoreTables.class.getName());
                    if (moreA != null && moreTableMap.size() != 0) {
                        handleJoinMoreData(m, f, v, moreTableMap);
                        continue;
                    }
                    //处理子表数据
                    MsJoinSubTable sbA = (MsJoinSubTable) annotationsCacheMap.get(cacheKey, MsJoinSubTable.class.getName());
                    if (sbA != null && subMap.size() != 0) {
                        handleJoinSubData(m, v, sbA, subMap);
                        continue;
                    }
                    //处理关联表数据
                    MsJoinTable msJoinTable = (MsJoinTable) annotationsCacheMap.get(cacheKey, MsJoinTable.class.getName());
                    if (msJoinTable != null && joinDataMap.size() != 0) {
                        handleTbData(m, v, msJoinTable, joinDataMap);
                    }
                }
            }
        }
    }


    /**
     * 处理字典数据核心方法
     *
     * @param m           实体对象
     * @param f           字段Field
     * @param v           字段值
     * @param dictA       该字段上的注解
     * @param dictDataMap 字典类型对应的值
     * @return
     * @author mashuai
     */

    private static <M> void handleDictData(M m, Field f, Object v, MsJoinDict dictA, Map<String, List<MsDictDto>> dictDataMap) throws Exception {
        if (dictA != null) {
            //字段需要进行映射的字典类型
            String dt = dictA.dictType();
            //获取该类型对应得值
            List<MsDictDto> dictDtoList = dictDataMap.get(dt);
            if (dictDtoList != null && dictDtoList.size() != 0) {
                //如果字典是多个值，优先对其进行处理---比如0;1
                String vStr = String.valueOf(v);
                String splitVal = DICT_SPLIT_VAL.equals(dictA.splitVal()) ? DICT_SPLIT_VAL : dictA.splitVal();
                //最终需要设置的值
                String vLabel = "";
                for (String sv : vStr.split(splitVal)) {
                    //遍历字典
                    for (MsDictDto dv : dictDtoList) {
                        if (dv.getValue().equals(sv)) {
                            //获取需要获取的字段值，默认是label标签，否则的话，根据needField去MsDictDto中获取
                            String needField = DICT_NEED_FIELD.equals(dictA.needField()) ? ApplicationConstant.msHbDictNeedFiled : dictA.needField();
                            vLabel += (DICT_NEED_FIELD.endsWith(needField) ? dv.getLabel() : MsReflexUtils.getClassFieldVal(dv, needField)) + splitVal;
                            break;
                        }
                    }
                }
                if (StringUtils.isNotEmpty(vLabel)) {
                    //去掉最后一个字符串
                    String newVLabel = MsConvertDataUtils.delStrLastDigitSymbol(vLabel);
                    String setField = dictA.setField();
                    //如果没有设置赋值字段，直接赋值给当前字段
                    if (MsEmptyUtils.isEmpty(setField)) {
                        f.set(m, newVLabel);
                    } else {
                        //赋值给指定字段
                        MsReflexUtils.setClassFieldVal(m, setField, newVLabel);
                    }
                }

            }
        }
    }

    /**
     * 逻辑：M实体字段上含有指定注解，全部在joinTbAList集合中，对其进行遍历，传入的需要传入的集合list，查询出每个字段需要的数据。
     * 对需要处理的field对象进行数据处理
     * 注解优先级：MsJoinMoreTables > @MsJoinSubTable > MsJoinTable
     */

    private static <M> void getJoinTableData(List<M> list, List<Field> joinTbAList, Map<String, List<Map<String, Object>>> joinTableMap, Map<String, List<?>> subMap, Map<String, List<Map<String, Object>>> moreTableMap, Set<String> excludeAnnotations) {
        //拼接@MsJoinTable只需要关联表单个字段值
        List<String> sqlList = new ArrayList<>();
        //拼接@MsJoinTable需要查询多个字段的sql
        List<String> moreSqlList = new ArrayList<>();
        //遍历需要处理的字段
        for (Field f : joinTbAList) {
            //跳过所需要排除的注解
            if (skipAnnotation(excludeAnnotations, f)) continue;
            //sql缓存的key，缓存每个字段上的所需要的sql语句,其sql存在优先级
            String joinSqlCacheKey = getCacheKey(f);
            //获取需要处理的数据，构建成字段集合，一次性查询出所有需要处理的数据
            List<String> ids = getListNeedFieldIds(list, f.getName(), f.getDeclaringClass().getName());
            if (ids.size() == 0) continue;
            //把条件拼接成in ('a1','a2')
            String idsStr = MsConvertDataUtils.joinInSql(ids);
            //处理含有@MsJoinMoreTables注解，获取list所需数据
            MsJoinMoreTables moreA = f.getAnnotation(MsJoinMoreTables.class);
            if (moreA != null) {
                getJoinMoreData(moreTableMap, moreA, idsStr, joinSqlCacheKey, f.getName());
                continue;
            }
            //处理@MsJoinSubTable，获取list所需数据
            MsJoinSubTable sbA = f.getAnnotation(MsJoinSubTable.class);
            if (sbA != null) {
                getSubTableData(subMap, sbA, idsStr);
                continue;
            }
            //处理MsJoinTable注解数据，这里只获取sql
            MsJoinTable jA = f.getAnnotation(MsJoinTable.class);
            if (jA != null) {
                handleJoinTableSql(jA, idsStr, sqlList, moreSqlList, joinSqlCacheKey);
            }
        }
        //处理数据---根据拼接好的单字段与多字段sql进行数据获取
        handleJoinData(joinTableMap, sqlList, moreSqlList);
    }

    /**
     * 处理MsJoinMoreTables注所需要的数据
     * select  t1.f1, t2.f2   from  t1 left join t2 on t1.id = t2.t1_id
     **/
    private static void getJoinMoreData(Map<String, List<Map<String, Object>>> moreTableMap, MsJoinMoreTables moreA, String idsStr, String joinSqlCacheKey, String fieldName) {
        //从缓存中获取所需要的sql片段
        String executeSql = sqlCacheMap.get(joinSqlCacheKey);
        if (executeSql == null) {
            MsJoinTable[] value = moreA.value();
            String selfCondition = moreA.condition();
            if (value.length == 0) return;
            if (value.length == 1) MsAssertUtils.throwInnerException(LE_MS_JOIN_TABLE);
            //select语句，用于查询结果列拼接
            StringBuilder selectSql = new StringBuilder();
            //from语句，用于拼接上left join条件
            StringBuilder fromSql = new StringBuilder();
            selectSql.append(" select   ");
            //where语句，拼接需要的条件
            StringBuilder whereSql = new StringBuilder();
            //记录上一个tableName
            String upTable = value[0].joinTable();
            for (int i = 0; i < value.length; i++) {
                MsJoinTable jA = value[i];
                String joinTable = jA.joinTable();
                //拼接关联条件
                String condition = jA.moreJoinCondition();
                if (MsEmptyUtils.isEmpty(condition) || !condition.contains("@")) {
                    MsAssertUtils.throwInnerException("请在类@字段：" + joinSqlCacheKey + "，字段上配置@MsJoinMoreTables[@MsJoinTable中配置关联条件，比如moreJoinCondition=id@room_id]");
                }
                String[] split = condition.split("@");
                String lLeft = split[0];
                String rLeft = split[1];
                if (i == 0) {
                    fromSql.append("  from   ").append(joinTable);
                    whereSql.append(" where ");
                    if (MsEmptyUtils.isNotEmpty(selfCondition)) {
                        whereSql.append(selfCondition).append(" and ");
                    }
                    whereSql.append(joinTable).append(".").append(rLeft).append(" in ");
                    selectSql.append(joinTable).append(".").append(rLeft).append(" as ").append(" val , ");
                } else {
                    fromSql.append(" left join  ").append(joinTable).append(" on ").append(upTable).append(".").append(lLeft).append(" = ").append(joinTable).append(".").append(rLeft);
                }
                upTable = joinTable;

                //拼接需要查询的列
                String[] needFields = jA.needFields();
                //用于拼接一个表需要获取多个字段时候，拼接，号的下标。
                int j = 0;
                if (needFields.length != 0) {
                    //获取name@roomName
                    for (String needField : needFields) {
                        //拆分name与roomName
                        String[] sp = needField.split("@");
                        String tbF = sp[0];
                        //需要赋值给实体类的字段
                        String bF = sp[1];
                        selectSql.append(joinTable).append(".").append(tbF).append(" as ").append("\"").append(bF).append("\"");
                        if (i < value.length - 1 || j != needFields.length - 1) {
                            selectSql.append(" , ");
                            j++;
                        }
                    }
                }
            }
            selectSql.append(fromSql.toString()).append(whereSql.toString());
            String selectStr = selectSql.toString();
            sqlCacheMap.put(joinSqlCacheKey, selectStr);
            //where id in  值是动态的，不可加入缓存
            executeSql = selectStr;
        }
        //查询sql
        executeSql += idsStr;
        SqlMapper sqlMapper = MsIocUtils.getBean(SqlMapper.class);
        List<Map<String, Object>> list = sqlMapper.executeSql(executeSql);
        moreTableMap.put(fieldName, list);
    }


    /**
     * 处理@MsJoinTable所需的sql语句
     **/
    private static void handleJoinTableSql(MsJoinTable jA, String idsStr, List<String> sqlList, List<String> moreSqlList, String joinSqlCacheKey) {
        String[] needFields = jA.needFields();
        if (needFields.length == 0) return;
        //尝试从缓存取值
        String executeSql = sqlCacheMap.get(joinSqlCacheKey);
        if (executeSql != null) {
            executeSql += idsStr;
            if (needFields.length == 1) {
                sqlList.add(executeSql);
            } else {
                moreSqlList.add(executeSql);
            }
            return;
        }
        //获取过滤条件
        String selfCondition = jA.condition();
        //优先使用@TableField的value值
        String joinTable = jA.joinTable();
        String joinField = TB_JOIN_FIELD.equals(jA.joinField()) ? ApplicationConstant.msHbTbJoinField : jA.joinField();
        //分两种情况查询，如果只查单个字段，进行union all拼接，如果查询多个字段，进行分开查询
        StringBuilder sql = new StringBuilder();
        if (needFields.length == 1) {
            String[] needFs = needFields[0].split("@");
            sql.append(" select  '").append(joinTable).append("'").append(" as \"joinTable\" ,").append(needFs[0]).append(" as \"needField\" , ").append(joinField).append(" as \"joinField\",  ")
                    .append("'").append(needFs[1]).append("'").append(" as \"setField\" ").append(" from ").append(joinTable).append(" where  ");
            if (MsEmptyUtils.isNotEmpty(selfCondition)) {
                sql.append(selfCondition).append(" and ");
            }
            sql.append(joinField).append(" in ");
            //加入缓存
            sqlCacheMap.put(joinSqlCacheKey, sql.toString());
            sqlList.add(sql.append(idsStr).toString());

        }
        //多字段映射，独立查询
        else {
            sql.append(" select ").append("'").append(joinTable).append("'").append(" as \"joinTable\" , ").append(joinField).append("  as \"joinField\"  ,  ");
            for (int i = 0; i < needFields.length; i++) {
                String[] split = needFields[i].split("@");
                String otherNeedField = split[0];
                String otherSetField = split[1];
                if (i < needFields.length - 1) {
                    sql.append(otherNeedField).append(" as ").append("\"").append(otherSetField).append("\"").append(",");
                } else {
                    sql.append(otherNeedField).append(" as ").append("\"").append(otherSetField).append("\"");
                }
            }
            sql.append(" from   ").append(joinTable).append(" where  ");
            if (MsEmptyUtils.isNotEmpty(selfCondition)) {
                sql.append(selfCondition).append(" and ");
            }
            sql.append(joinField).append(" in ");
            sqlCacheMap.put(joinSqlCacheKey, sql.toString());
            moreSqlList.add(sql.append(idsStr).toString());
        }
    }


    /**
     * 是否开启SQL片段缓存
     **/
    private static Boolean isOpenHbSqlCache() {
        return "true".equals(ApplicationConstant.msCacheHbSql);
    }

    /**
     * 获取sql缓存的key
     **/
    private static String getCacheKey(Field f) {
        String className = f.getDeclaringClass().getName();
        String fieldName = f.getName();
        String cacheKey = className + "@" + fieldName;
        return cacheKey;
    }

    /**
     * 排除XXX注解不参与检查
     **/
    private static Set<String> excludeAnnotation(Class<? extends Annotation>... args) {
        Set<String> excludeSet = new HashSet<>();
        if (args != null) {
            for (Class<? extends Annotation> arg : args) {
                String argName = arg.getName();
                if (MsJoinDict.class.getName().equals(argName)) {
                    excludeSet.add(argName);
                } else if (MsJoinMoreTables.class.getName().equals(argName)) {
                    excludeSet.add(argName);
                } else if (MsJoinSubTable.class.getName().equals(argName)) {
                    excludeSet.add(argName);
                } else if (MsJoinTable.class.getName().equals(argName)) {
                    excludeSet.add(argName);
                }
            }
        }
        return excludeSet;
    }

    /**
     * 跳过不需要处理的注解
     **/
    private static Boolean skipAnnotation(Set<String> excludeSet, Field f) {
        if ((f.isAnnotationPresent(MsJoinDict.class) && excludeSet.contains(MsJoinDict.class.getName()))
                || (f.isAnnotationPresent(MsJoinMoreTables.class) && excludeSet.contains(MsJoinMoreTables.class.getName()))
                || (f.isAnnotationPresent(MsJoinSubTable.class) && excludeSet.contains(MsJoinSubTable.class.getName()))
                || (f.isAnnotationPresent(MsJoinTable.class) && excludeSet.contains(MsJoinTable.class.getName()))) {
            return true;
        }
        return false;
    }

    /**
     * 处理子表关联的数据，
     *
     * @param subMap
     * @param sbA
     * @param idsStr
     * @return
     * @author mashuai
     */

    private static <M> void getSubTableData(Map<String, List<?>> subMap, MsJoinSubTable sbA, String idsStr) {
        StringBuilder sql = new StringBuilder();
        sql.append(" select *from  ").append(sbA.joinTable()).append(" where ").append(sbA.joinField()).append(" in  ").append(idsStr);
        if (StringUtils.isNotEmpty(sbA.condition())) {
            sql.append(" and  ").append(sbA.condition());
        }
        SqlMapper sqlMapper = MsIocUtils.getBean(SqlMapper.class);
        List<Map<String, Object>> list = sqlMapper.executeSql(sql.toString());
        if (list.size() == 0) return;
        //逻辑。先根据绑定的id值进行分组，然后将每组的map转bean
        String joinField = sbA.joinField();
        //判断是否开启了驼峰,也就是List<Map>结果都key转换成驼峰
        if ("true".equals(ApplicationConstant.msMpCamel) || "true".equals(ApplicationConstant.msHbOpenCamel)) {
            joinField = MsConvertDataUtils.lineToCamel(joinField);
        }
        String finalJoinField = joinField;
        try {
            Map<String, List<Map<String, Object>>> collect = list.stream().collect(Collectors.groupingBy(t -> MapUtils.getString(t, finalJoinField)));
            Class<?> clazz = sbA.clazz();
            //判断是否为默认的List<Map>类型
            boolean def = clazz.getName().equals(Map.class.getName());
            collect.keySet().forEach(t -> subMap.put(t, def ? collect.get(t) : MsConvertDataUtils.listMapToListBean(collect.get(t), sbA.clazz())));

        } catch (Exception e) {
            MsAssertUtils.throwException("查询结果集合中不包含【" + joinField + "】字段，无法对其进行分组，请优先检查是否开启了List<Map>结果转换驼峰的配置mybatis-plus.configuration.mapUnderscoreToCamelCase=true ：" + e.getMessage());
        }
    }

    /**
     * 处理关联表信息
     *
     * @param m
     * @param v
     * @param tbA
     * @param joinDataMap
     * @return
     * @author mashuai
     */

    private static <M> void handleTbData(M m, Object v, MsJoinTable tbA, Map<String, List<Map<String, Object>>> joinDataMap) throws Exception {
        String joinTable = tbA.joinTable();
        String[] needFields = tbA.needFields();
        if (needFields.length != 1) {
            joinTable += "@more";
        }
        List<Map<String, Object>> list = joinDataMap.get(joinTable);
        if (list == null || list.size() == 0) return;
        for (Map<String, Object> map : list) {
            String joinFieldVal = MapUtils.getString(map, "joinField");
            if (v.equals(joinFieldVal) || String.valueOf(v).equals(joinFieldVal)) {
                //设置单个字段
                if (!joinTable.contains("@more")) {
                    Object fV = MapUtils.getObject(map, "needField");
                    String setFieldName = MapUtils.getString(map, "setField");
                    MsReflexUtils.setClassFieldVal(m, setFieldName, fV);
                }
                //设置多个字段
                else {
                    for (String nF : needFields) {
                        String setFieldName = nF.split("@")[1];
                        Object fV = MapUtils.getObject(map, setFieldName);
                        MsReflexUtils.setClassFieldVal(m, setFieldName, fV);
                    }
                }
                break;
            }
        }
    }


    /**
     * 处理多表的数据
     **/
    private static <M> void handleJoinSubData(M m, Object v, MsJoinSubTable sbA, Map<String, List<?>> subMap) throws Exception {
        List<?> objects = subMap.get(String.valueOf(v));
        if (objects != null) {
            MsReflexUtils.setClassFieldVal(m, sbA.setField(), objects);
        }
    }

    /**
     * 处理多层left join数据
     **/
    private static <M> void handleJoinMoreData(M m, Field f, Object v, Map<String, List<Map<String, Object>>> moreMap) throws Exception {
        List<Map<String, Object>> list = moreMap.get(f.getName());
        if (list != null && list.size() != 0) {
            Map<String, List<Map<String, Object>>> valMap = list.stream().collect(Collectors.groupingBy(t -> MapUtils.getString(t, "val")));
            List<Map<String, Object>> result = valMap.get(v);
            if (result != null && result.size() != 0) {
                Map<String, Object> map = result.get(0);
                for (String key : map.keySet()) {
                    if (!"val".equals(key)) {
                        MsReflexUtils.setClassFieldVal(m, key, MapUtils.getString(map, key));
                    }
                }
            }
        }
    }


    /**
     * 处理返回的结果
     *
     * @param result
     * @param sqlList     单字段信息
     * @param moreSqlList 多字段信息
     * @return
     * @author mashuai
     */

    private static void handleJoinData(Map<String, List<Map<String, Object>>> result, List<String> sqlList, List<String> moreSqlList) {
        SqlMapper mapper = MsIocUtils.getBean(SqlMapper.class);
        if (sqlList.size() != 0) {
            List<Map<String, Object>> joinSingleData = mapper.executeSql(joinUnionAllSql(sqlList));
            Map<String, List<Map<String, Object>>> joinTableMap = joinSingleData.stream().collect(Collectors.groupingBy(t -> MapUtils.getString(t, "joinTable")));
            result.putAll(joinTableMap);
        }
        if (moreSqlList.size() != 0) {
            for (String sql : moreSqlList) {
                List<Map<String, Object>> moreDataList = mapper.executeSql(sql);
                Map<String, List<Map<String, Object>>> joinTableMap = moreDataList.stream().collect(Collectors.groupingBy(t -> MapUtils.getString(t, "joinTable") + "@more"));
                result.putAll(joinTableMap);
            }
        }
    }

    /**
     * @param m
     * @param fList
     * @return
     * @author mashuai
     */

    private static <M> List<Map<String, Object>> executeSaveCheck(M m, List<Field> fList) throws Exception {
        List<String> sqlList = new ArrayList<>(fList.size());
        for (Field f : fList) {
            MsSaveCheck scA = f.getAnnotation(MsSaveCheck.class);
            String bizField = SAVE_BIZ_FILED.equals(scA.bizField()) ? ApplicationConstant.msHbSaveBizFiled : scA.bizField();
            String delFlag = DEL_DEL_FIELD.equals(scA.delFlag()) ? ApplicationConstant.msHbDelSing : scA.delFlag();
            String errorMsg = scA.errorMsg();
            //优先使用@TableField值
            String checkColumn = f.getName();
            TableField tfA = f.getAnnotation(TableField.class);
            if (tfA != null) {
                checkColumn = tfA.value();
            }
            //优先使用自定义检测表名
            String checkTable = scA.checkTable();
            if (MsEmptyUtils.isEmpty(checkTable)) {
                TableName tnA = m.getClass().getAnnotation(TableName.class);
                checkTable = tnA.value();
                MsAssertUtils.objIsNull(checkTable, NO_SAVE_CHECK_TABLE);
            }
            //拼接sql
            StringBuilder sql = new StringBuilder();
            sql.append(" select count(*) as \"nums\" , ").append(checkColumn).append(" as \"fieldVal\", ").append("'").append(checkColumn).append("'").append(" as \"checkColumn\" ,").append("'").append(errorMsg)
                    .append("'").append(" as \"errorMsg\" ").append(" from ").append(checkTable).append(" where 1 =1   ").append(" and ").append(checkColumn).append(" = ").append("'").append(MsReflexUtils.getClassFieldVal(m, f.getName())).append("'");
            if (MsEmptyUtils.isNotEmpty(delFlag)) {
                sql.append("  and ").append(delFlag);
            }
            //排除数据条件不可为空
            if (MsEmptyUtils.isNotEmpty(bizField)) {
                Object biV = MsReflexUtils.getClassFieldVal(m, bizField);
                if (biV != null && MsEmptyUtils.isNotEmpty(String.valueOf(biV))) {
                    sql.append(" and  ").append(bizField).append(" != ").append("'").append(biV).append("'");
                }
            }
            sql.append(" GROUP BY  ").append(checkColumn);
            sqlList.add(sql.toString());
        }
        //拼接sql
        String sql = joinUnionAllSql(sqlList);
        SqlMapper sqlMapper = MsIocUtils.getBean(SqlMapper.class);
        List<Map<String, Object>> resultList = sqlMapper.executeSql(sql);
        return resultList;
    }

    /**
     * 处理保存检查的数据结果
     *
     * @param m
     * @param throwException
     * @return
     * @author mashuai
     */

    private static <M> List<MsSaveCheckDto> handleSaveCheckM(M m, boolean throwException) throws Exception {
        if (m != null) {
            //解析字段上的@MsSaveCheck注解
            List<Field> fList = MsReflexUtils.getClassFieldA(m, MsSaveCheck.class);
            if (fList.size() != 0) {
                //检验
                List<Map<String, Object>> resultList = executeSaveCheck(m, fList);
                //默认抛出异常，如果不抛出异常，返回检验信息，让使用者自己处理业务即可
                if (throwException) {
                    List<String> msgList = new ArrayList<>();
                    for (Map<String, Object> map : resultList) {
                        if (MapUtils.getInteger(map, "nums") != 0) {
                            String errorMsg = MapUtils.getString(map, "errorMsg");
                            if (MsEmptyUtils.isNotEmpty(errorMsg)) {
                                msgList.add(errorMsg);
                            } else {
                                msgList.add("字段【" + MapUtils.getString(map, "checkColumn") + "】值【" + MapUtils.getString(map, "fieldVal") + "】已经存在");
                            }
                        }
                    }
                    if (msgList.size() != 0) {
                        MsAssertUtils.throwException(msgList.stream().collect(Collectors.joining(ApplicationConstant.msHbSaveCheckSplit)));
                    }
                }
                //强制转换成List<MsSaveCheckDto>
                return MsConvertDataUtils.listMapToListBean(resultList, MsSaveCheckDto.class);
            }
        }
        return new ArrayList<>();
    }


    /**
     * 处理删除前的检验
     **/
    private static <M, S> List<MsDelCheckDto> handleDelCheckM(List<S> list, Class<M> clazz, boolean throwException) {
        if (list.getClass().getName().contains("Arrays")) MsAssertUtils.throwException(DEL_CHECK_NO_ARRAYS);
        List<MsDelCheckDto> result = new ArrayList<>();
        List<Field> fList = MsReflexUtils.getClassFieldA(clazz, MsDelCheck.class);
        //如果未加@MsDelCheck，允许全部删除
        if (fList == null || fList.size() == 0) return result;
        //执行检查sql并且获取其结果
        result = executeDelCheck(list, fList);
        if (result.size() == 0) return result;
        //如果不存在被引用的数据，返回原有的集合
        List noPassIds = result.stream().map(MsDelCheckDto::getTf).distinct().collect(Collectors.toList());
        if (noPassIds.size() == 0) return result;
        list.removeAll(noPassIds);
        //如果存在引用数据，根据throwException来判断
        if (throwException) {
            StringBuilder msg = new StringBuilder();
            //判断是否存在自定义异常信息
            MsDelCheck mD = fList.get(0).getAnnotation(MsDelCheck.class);
            if (MsEmptyUtils.isNotEmpty(mD.errorMsg()))
                MsAssertUtils.throwException(mD.errorMsg());
            //自己构建异常信息
            for (MsDelCheckDto t : result) {
                if (t.getNum() != 0) {
                    msg.append("待删除的数据标识值为【").append(t.getTf()).append("】").append("已经被表【").append(t.getTb()).append("】").append("中").append("【").append(t.getFN()).append("】")
                            .append("字段的【").append(t.getTipMsg()).append("】").append("值进行引用@");
                }
            }
            MsAssertUtils.throwException(MsConvertDataUtils.delStrLastDigitSymbol(msg.toString()));
        }
        return result;
    }

    /**
     * 检查删除的字段是否被引用
     **/
    private static <S> List<MsDelCheckDto> executeDelCheck(List<S> list, List<Field> fList) {
        List<String> sqlList = new ArrayList<>();
        List<MsDelCheckDto> result = new ArrayList<>();
        for (Field f : fList) {
            MsDelCheck dA = f.getAnnotation(MsDelCheck.class);
            String condition = dA.condition();
            String[] checkTable = dA.checkTable();
            if (checkTable.length == 0) MsAssertUtils.throwException(NO_DEL_CHECK_NO_CT);
            //如果保留默认值，优先使用配置文件的配置
            String delFlag = dA.delFlag().equals(DEL_DEL_FIELD) ? ApplicationConstant.msHbDelSing : dA.delFlag();
            for (String ct : checkTable) {
                String[] ctAr = ct.split("@");
                if (ct.length() < 2) MsAssertUtils.throwException(NO_DEL_CHECK_NO_CT);
                String tbName = ctAr[0];
                String tbField = ctAr[1];
                String tipField = tbField;
                if (ctAr.length == 3) tipField = ctAr[2];
                StringBuilder sql = new StringBuilder();
                sql.append(" select  '").append(tbName).append("' as \"tb\" ,").append(tbField).append(" as \"tf\", ").append(" count(*) as \"num\" ,")
                        .append("'").append(tipField).append("'").append(" as \"fN\" ,").append(" GROUP_CONCAT(").append(tipField)
                        .append(") as \"tipMsg\"  ").append(" from ").append(tbName).append(" where ").append(tbField).append(" in ").append(MsConvertDataUtils.joinInSql(list));
                if (MsEmptyUtils.isNotEmpty(delFlag)) sql.append(" and ").append(delFlag);
                if (MsEmptyUtils.isNotEmpty(condition)) sql.append(" and ").append(condition);
                sql.append(" group by ").append(tbField);
                sqlList.add(sql.toString());
            }
            String uSql = joinUnionAllSql(sqlList);
            SqlMapper sqlMapper = MsIocUtils.getBean(SqlMapper.class);
            result.addAll(sqlMapper.executeDelCheckSql(uSql));
        }
        return result;
    }

    //TODO 一些公共方法区

    /**
     * 从数据集合中，过滤出需要查询字段数据集合
     *
     * @param list      待查询的数据集合
     * @param fieldName 字段名称
     * @param clazzName 类名
     * @return
     * @author mashuai
     */

    private static <M> List<String> getListNeedFieldIds(List<M> list, String fieldName, String clazzName) {
        List<String> joinFieldList = list.stream().map(m -> {
            try {
                Field needField = m.getClass().getDeclaredField(fieldName);
                needField.setAccessible(true);
                return String.valueOf(needField.get(m));
            } catch (Exception e) {
                MsAssertUtils.throwException("在类：" + clazzName + "，获取字段" + fieldName + "值异常");
                return null;
            }

        }).collect(Collectors.toList());
        return joinFieldList;
    }


    /**
     * 拼接union all
     *
     * @param collect
     * @return
     * @author mashuai
     */

    private static String joinUnionAllSql(List<String> collect) {
        StringBuilder sql = new StringBuilder();
        for (int i = 0; i < collect.size(); i++) {
            if (i < collect.size() - 1) {
                sql.append(collect.get(i));
                sql.append(" union  all ");
            } else {
                sql.append(collect.get(i));
            }
        }
        return sql.toString();
    }


    /***
     * sql执行器
     * **/
    @Component
    private interface SqlMapper extends BaseMapper {
        @Select(" ${sql} ")
        List<Map<String, Object>> executeSql(@Param("sql") String sql);

        @Select(" ${sql} ")
        List<MsDelCheckDto> executeDelCheckSql(@Param("sql") String sql);
    }

}
